/* 
 * Copyright 2010 Ziya Guven Koylu, Ozge Koylu
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at 
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
package com.koylu.caffein.model.handler;

import java.lang.reflect.Method;

import javassist.util.proxy.MethodHandler;

import org.apache.commons.beanutils.PropertyUtils;

import com.koylu.caffein.Cascade;
import com.koylu.caffein.model.caffein.CaffeinConfig;
import com.koylu.caffein.model.clazz.Clazz;
import com.koylu.caffein.model.clazz.ManyToMany;
import com.koylu.caffein.model.clazz.ManyToOne;
import com.koylu.caffein.model.clazz.Relation;
import com.koylu.caffein.model.clazz.id.Id;
import com.koylu.caffein.model.container.ClazzContainer;
import com.koylu.caffein.model.container.ManyToManyContainer;

public class ClazzContainerHandler implements MethodHandler {

	private CaffeinConfig caffeinConfig;
	private Clazz clazz;
	private Id clazzId;
	private Object id;
	private String containerUniqueId;
	private boolean updated = false;

	public ClazzContainerHandler(CaffeinConfig caffeinConfig, Clazz clazz, Object id) throws Exception {
		this.caffeinConfig = caffeinConfig;
		this.clazz = clazz;
		clazzId = clazz.getId();
		this.id = clazzId.copy(caffeinConfig, id);
	}

	public ClazzContainerHandler(CaffeinConfig caffeinConfig, Clazz clazz, Object id, boolean updated) throws Exception {
		this(caffeinConfig, clazz, id);
		this.updated = updated;
	}
	
	public Object invoke(Object object, Method method, Method proceed, Object[] args) throws Throwable {
		String methodName = method.getName();
		if(clazz.isReadonly()){
			if (methodName.startsWith("set")) {
				return null;
			}
		}
		if ("getContainerId".equals(methodName)) {
			return id;
		} else if ("getContainerUniqueId".equals(methodName)) {
			if(containerUniqueId == null){
				containerUniqueId = clazzId.toUniqueId(caffeinConfig, id);
			}
			return containerUniqueId;
		} else if ("isContainerUpdated".equals(methodName)) {
			if (updated) {
				return true;
			} else {//Check ManyToOne objects
				if (clazz.getRelationMap() != null) {
					for (Relation relation : clazz.getRelationMap().values()) {
						if (relation instanceof ManyToOne) {
							if (((ClazzContainer) PropertyUtils.getProperty(object, relation.getName())).isContainerIdUpdated()) {
								return true;
							}
						} else if( relation instanceof ManyToMany ){
							ManyToMany manyToMany = (ManyToMany)relation;
							if (Cascade.ALL.equals(Cascade.valueOf(manyToMany.getCascade()))) {
								ManyToManyContainer manyToManyContainer = (ManyToManyContainer) PropertyUtils.getProperty(object, manyToMany.getName());
								if (manyToManyContainer.getContainerCollection() != null) {
									if( manyToManyContainer.getRemovedObjects().size() > 0 ){
										return true;
									} else if( manyToManyContainer.getAddedObjects().size() > 0 ){
										return true;
									} else if ( manyToMany.getIndex() != null ){
										return manyToManyContainer.isIndexChanged();
									}
								}
							}
							return false;
						}
					}
				}
			}
			return false;
		} else if ("isContainerIdUpdated".equals(methodName)) {
			if(containerUniqueId == null){
				containerUniqueId = clazzId.toUniqueId(caffeinConfig, id);
			}
			return !containerUniqueId.equals(clazzId.toUniqueId(caffeinConfig, PropertyUtils.getProperty(object, clazzId.getName())));
		} else if("equals".equals(methodName)){
			if(args[0] instanceof ClazzContainer){
				return clazzId.toUniqueId(caffeinConfig, PropertyUtils.getSimpleProperty(object, clazzId.getName())).equals(clazzId.toUniqueId(caffeinConfig, PropertyUtils.getSimpleProperty(args[0], clazzId.getName())));
			}
			return false;
		} else if("hashCode".equals(methodName)){
			return -1; 
		}
		if (!updated) {
			if (methodName.startsWith("set")) {
				updated = true;
			}
		}
		return proceed.invoke(object, args);
	}
}